package mergeBymicroRNA;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import cerna_pairs.Time;


/**+
 * 2016.11.04
 * @author zhang
 * 将miranda的结果，按照microRNA的名字分类，merge好，然而，想了一下，完全没有必要写成java，bash也可以做，但是待会还是打算写一下，不写java的能力就废了；）
 * 然而我想的还是太简单，由于microRNA太多，所以循环比较大，bash循环的速度太慢了，可以想象随后R脚本的处理速度
 * 所以下一个java脚本，争取加快一下速度
 * -p应该可行，可以多线程输出
 * 2016.11.04问题解决，花了大概5个多小时，难度还是有点的，所以挺自豪的
 */
public class merge {
	public static void main(String[] args){
		Time time = new Time();	// 新建的自定义time类，用来输出运行时间
		int numThread = 1;	// 最终程序要使用的线程数，默认为1
		Isfileexist exist = new Isfileexist();
		int filenum = 1;	// 判断具有多少个输入文件
		Map<String, Map<String, List<String>>> miranda = new HashMap<String, Map<String, List<String>>>();	
		// 本来想写一个自定义类，但是完全没有必要，两个map即可
		// miranda是microRNA和RNA之间的关系，type是RNA及RNA类型的汇总
		String content = null;
		
		if(args.length == 0 || args[0].equals("-h") || args[0].equals("--help")){
			System.out.println("该程序用来处理miranda的结果");
			System.out.println("设计目标虽然可设定为输入多个文件，但是还是以一个为重点");
			System.out.println("文件一共三列，第一列为microRNA");
			System.out.println("第二列为其他RNA的标识");
			System.out.println("第三类为文件的类型" + "\n");
			System.out.println("#############################################################################" + "\n");
			System.out.println("参数要求：输入文件，输出目录");
			System.out.println("支持-p设定多线程数，主要用于多线程输出");
			System.out.println("当然各种参数在最后添加");
			return;	// 惯例输出使用说明，由于没有使用if-else，加一个return，终止程序
		}
		
		
		long start = System.currentTimeMillis();
		
		for(int i = 0; i < args.length; i++){	//	通过循环遍历所有参数，设置线程数
			if(args[i].equals("-p")){
				filenum = i - 1;
				if(i < (args.length - 1)){	//	判断-p后有int值，如果没有，使用默认值1
					numThread = Integer.parseInt(args[i + 1]);
				}else{
					System.out.println("-p参数没有添加线程数，默认：1");
				}			
			}
		}		
		
		File outdir = new File(args[filenum]);		
		exist.isDirExist(outdir);
		
		try {
			for(int i = 0; i < filenum; i++){
				File input = new File(args[i]);
				
				if(!exist.isExist(input)){
					return;
				}
				
				FileReader fr = new FileReader(input);
				BufferedReader br = new BufferedReader(fr); 
				
				while((content = br.readLine()) != null){
					String[] contentarray = content.split("\\s+");
					
					if(miranda.containsKey(contentarray[0])){	// 判断是否已经包含有这个microRNA了，然后分别采用不同处理
						Map<String, List<String>> type = miranda.get(contentarray[0]);
						
						if(type.containsKey(contentarray[2])){	// 判断这一类microRNA中是否已经有这中类型了，然后分别处理
							List<String> rna = type.get(contentarray[2]);
							rna.add(contentarray[1]);
							type.put(contentarray[2], rna);
						}else{
							List<String> rna = new ArrayList<String>();
							rna.add(contentarray[1]);
							type.put(contentarray[2], rna);
						}
						
						miranda.put(contentarray[0], type);
					}else{
						Map<String, List<String>> type = new HashMap<String, List<String>>();
						
						if(type.containsKey(contentarray[2])){
							List<String> rna = type.get(contentarray[2]);
							rna.add(contentarray[1]);
							type.put(contentarray[2], rna);
						}else{
							List<String> rna = new ArrayList<String>();
							rna.add(contentarray[1]);
							type.put(contentarray[2], rna);
						}
						
						miranda.put(contentarray[0], type);
					}
					
				}
				System.out.println(input + "读取已完成");			
			}
			
			Set<String> micro = miranda.keySet();	// 提取microRNA
			int blockSize = micro.size()/numThread;
			
			

			List<List<String>> multiKeySet = new ArrayList<List<String>>(numThread);	// 分割之后的文件
							
				
			for(int i = 1; i <= numThread; i++){	//		根据线程数，建立一个循环，用来分割线程
				List<String> tem = new ArrayList<String>();	// 新建的临时List类，单独收集分割出来的内从
				if(i < numThread){
					for(int j = (i - 1)*blockSize; j < i * blockSize; j++){
						tem.add(micro.toArray()[j].toString());
					}
				}else if(i == numThread){
					for(int j = (i - 1)*blockSize; j < micro.size(); j++){
						tem.add(micro.toArray()[j].toString());
					}
				}
				multiKeySet.add(tem);
			}
		
		// 不论单线程还是多线程，都可以按照一个套路计算
			List<Thread> threads = new ArrayList<Thread>(numThread);
			for(int i = 0; i < numThread; i++){
				Multi testMulti = new Multi(multiKeySet.get(i), miranda, args[filenum]);
				threads.add(new Thread(testMulti));
			}
			
			for(int i = 0; i < numThread; i++){
				threads.get(i).start();
			}
			
			Thread.sleep(4000L);						//	主线程沉睡
			
			for(int i = 0; i < numThread; i++){
				threads.get(i).join();
			}
		} catch (IOException | InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		System.out.println("程序运行结束！");
		long end = System.currentTimeMillis();
		time.run_time(start, end);
		
	}
	
	private static String delete(String path){
		String results;
		
		if(path.substring((path.length() - 1), path.length()).equalsIgnoreCase("/")){ // 判断，如果字符串最后一位是/,就去掉/
			results = path.substring(0, (path.length() - 1));
		}else{
			results = path;
		}
		return results;
	}
	
	// 多线程
	private static class Multi implements Runnable{
		private List<String> key;
		private Map<String, Map<String, List<String>>> miranda;
		private String path;
		// 设置多线程
		public Multi(List<String> key, Map<String, Map<String, List<String>>> miranda, String path){
			this.key = key;
			this.miranda = miranda;
			this.path = delete(path);
		}
				
		// 尝试用递归解决问题		
		private static List<String> extract(List<String> lastOne, List<String> thisOne, String type){
			List<String> results = new ArrayList<String>();
			if(lastOne == null){	// 针对第一个输入极有可能是null的情况
				for(int i = 0; i < thisOne.size(); i++){
					results.add(thisOne.get(i) + "\t" + type);
				}
			}else{	// 随后有多少个输入都重新反复两个遍历，全部添加一遍，然后返回
				for(int i = 0; i < lastOne.size(); i++){
					for(int j = 0; j < thisOne.size(); j++){
						String tem = lastOne.get(i) + "\t" + thisOne.get(j) + "\t" + type;
						results.add(tem);
					}
				}
			}		
			return results;
		}
		
		// 尝试从自己的<String, List<String>>类中遍历出成为我想要的字符串数组
		private static List<String> extractFromMap(Map<String, List<String>> type){
			Set key = type.keySet();
			List<String> output = null;
			
			if(key.size() == 1){	// 如果key只有一个，那就直接怎么读进来的，怎么输出去
				output = type.get(key.toArray()[0].toString());
			}else{		
				for(int i = 0; i < key.size(); i++){	// 如果key够多，就调用extract函数，反复调用，思想是利用递归的思想
					output = extract(output, type.get(key.toArray()[i].toString()), key.toArray()[i].toString());
				}
			}
			return output;
		}
		
		// 开始计算
		public void run(){
			for(int i = 0; i < key.size(); i++){
				File output = new File(path + "/" + key.get(i));	// 给每个key新建一个文件
				System.out.println("计算" + key.get(i) + "中");
				
				if(!(output.exists() && output.isFile())){	 // 如果文件不存在，就新建一个
					try {
						output.createNewFile();
					} catch (IOException e) {
						System.out.println(output + "创建失败");
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
				try {
					FileWriter fw = new FileWriter(output);
					BufferedWriter bw = new BufferedWriter(fw);
					
					Map<String, List<String>> type = miranda.get(key.get(i));
					
					/*
					 * 原本我面临的一个问题是，将类型整合成了一个Map<Map<List>>,
					 * 我想将这个中间的Map有n个元素，我想将这个n个，串联在一起形成一行n列
					 * 难点就在于次，单个遍历不难，如何多个遍历还能整合
					 * 设计了extractFromMap，以递归的方式解决这个问题 
					 */
					List<String> results = extractFromMap(type);
										
					for(int j = 0; j < results.size(); j++){
						bw.write(key.get(i) + "\t" + results.get(j));
						bw.newLine();
					}
					
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}				
			}
		}	
	}
}
